package com.kaithan.framework.common.util;

import cn.idev.excel.FastExcel;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.kaithan.framework.common.excel.BaseExcelListener;
import com.kaithan.framework.common.excel.DynamicHeaderExcelListener;
import com.kaithan.framework.common.exception.MyException;
import com.kaithan.framework.common.vo.RawExcelVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * 类描述：excel读取、写入工具类
 *
 * @author ZhuYin
 * @since 2025年02月05日
 */
@Slf4j
public class FastExcelUtil {

    /**
     * 方法说明：读取excel文件为对象集合
     * 默认第1行为标题，第2行为表头，从第3行开始为数据行
     * @param excelFile     excel文件对象
     * @return              返回读取到的对象集合
     * @author ZhuYin
     * @since 2024年05月31日
     */
    public static RawExcelVO readExcel(MultipartFile excelFile) {
        return readExcel(excelFile, 2);
    }

    /**
     * 方法说明：读取excel文件为对象集合
     *
     * @param excelFile    excel文件对象
     * @param headerRowNum excel文件中表头所在的行号
     * @return 返回读取到的数据集合(集合最后一列为当前行的行号)
     * @author ZhuYin
     * @since 2024年05月31日
     */
    public static RawExcelVO readExcel(MultipartFile excelFile, Integer headerRowNum) {
        Instant start = Instant.now();
        try (InputStream excelInputStream = excelFile.getInputStream()) {
            // 表头属性中文名称集合
            List<String> headerNameList = Lists.newArrayList();
            // 读取到的数据集
            List<LinkedHashMap<Integer, String>> dataList = Lists.newArrayList();
            FastExcel.read(excelInputStream)
                    // 默认读取第一个sheet
                    .sheet()
                    // 设置表头所在行号
                    .headRowNumber(headerRowNum)
                    .registerReadListener(new DynamicHeaderExcelListener(headerNameList, dataList))
                    .doRead();
            log.info("读取Excel数据，总行数：{} 耗时：{}s", dataList.size(), ChronoUnit.SECONDS.between(start, Instant.now()));

            List<LinkedHashMap<Integer, String>> finalList = Lists.newArrayList();
            // 将空行去除
            if (CollectionUtils.isNotEmpty(dataList)) {
                for (LinkedHashMap<Integer, String> row : dataList) {
                    Collection<String> values = row.values();
                    if (values.stream().anyMatch(StringUtils::isNotBlank)) {
                        finalList.add(row);
                    }
                }
            }
            return RawExcelVO.builder().headerNameList(headerNameList).dataList(finalList).build();
        } catch (Exception e) {
            log.error("读取上传的Excel异常:", e);
            throw new MyException("读取Excel内容失败");
        }
    }

    /**
     * 方法说明：读取本地excel文件内容
     * 默认第1行为标题，第2行为表头，从第3行开始为数据行
     *
     * @param excelPath excel绝对全路径
     * @return 类描述：未做任何处理读取到的excel内容VO
     * @author ZhuYin
     * @since 2024年06月22日
     */
    public static RawExcelVO readExcel(String excelPath) {
        return readExcel(excelPath, 2);
    }

    /**
     * 方法说明：读取指定路径下excel中的数据
     *
     * @param excelPath    excel绝对全路径
     * @param headerRowNum 指定的表头行号
     * @return 类描述：未做任何处理读取到的excel内容VO
     * @author ZhuYin
     * @since 2024年06月22日
     */
    public static RawExcelVO readExcel(String excelPath, Integer headerRowNum) {
        Instant start = Instant.now();
        // 创建监听器
        DynamicHeaderExcelListener listener = new DynamicHeaderExcelListener();
        // 读取 Excel 文件
        FastExcel.read(excelPath, listener)
                // 可能需要自定义转换器
                // .registerReadConverter(new CustomStringConverter())
                .headRowNumber(headerRowNum - 1)
                .sheet()
                .doRead();

        List<LinkedHashMap<Integer, String>> dataList = listener.getDataList();
        List<String> excelHeaderNameList = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(dataList)) {
            excelHeaderNameList = Lists.newArrayList(dataList.subList(0, 1).get(0).values());
        }

        List<LinkedHashMap<Integer, String>> finalList = Lists.newArrayList();
        // 将空行去除，补充末尾行缺少的列值
        if (CollectionUtils.isNotEmpty(dataList)) {
            for (LinkedHashMap<Integer, String> row : dataList) {
                LinkedHashMap<Integer, String> finalRow = Maps.newLinkedHashMap();
                List<String> values = new ArrayList<>(row.values());
                List<String> realValues = values.subList(0, values.size() - 1);
                if (realValues.stream().anyMatch(StringUtils::isNotBlank)) {
                    for (int i = 0; i < excelHeaderNameList.size() - 1; i++ ) {
                        String cellValue = row.get(i);
                        String val = "";
                        if (StringUtils.isNotBlank(cellValue)) {
                            val = cellValue;
                        }
                        finalRow.put(i, val);
                    }
                    finalList.add(finalRow);
                }
            }
        }

        log.info("读取Excel数据，总行数：{} 耗时：{}s", finalList.size(), ChronoUnit.SECONDS.between(start, Instant.now()));
        return RawExcelVO.builder()
                .headerNameList(excelHeaderNameList)
                .dataList(finalList)
                .build();
    }

    /**
     * 方法说明：读取excel数据转成对应的实体类
     *
     * @param excelFile     excel文件对象
     * @param headerRowNum  表头所在的行号
     * @param clazz         要转换成的对象
     * @return 返回读取到的数据集合
     * @author ZhuYin
     * @since 2025年02月05日
     */
    public static <T> List<T> readExcel(MultipartFile excelFile, Integer headerRowNum, Class<T> clazz) {
        Instant start = Instant.now();
        try (InputStream excelInputStream = excelFile.getInputStream()) {
            List<T> list = FastExcel.read(excelInputStream)
                    // 默认读取第一个sheet
                    .sheet()
                    // 设置表头所在行号
                    .headRowNumber(headerRowNum)
                    .head(clazz)
                    .registerReadListener(new BaseExcelListener<T>())
                    .doReadSync();
            log.info("读取Excel数据，总行数：{} 耗时：{}s", list.size(), ChronoUnit.SECONDS.between(start, Instant.now()));
            return list;
        } catch (Exception e) {
            log.error("读取上传的Excel异常:", e);
            throw new MyException("读取Excel内容失败");
        }
    }

    /**
     * 方法说明：读取excel数据转成对应的实体类
     * 默认第1行为标题，第2行为表头，从第3行开始为数据行
     * @param excelFile excel文件对象
     * @param clazz     要转换成的对象
     * @return 返回读取到的数据集合
     * @author ZhuYin
     * @since 2025年02月05日
     */
    public static <T> List<T> readExcel(MultipartFile excelFile, Class<T> clazz) {
        return readExcel(excelFile, 2, clazz);
    }
}
